﻿using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;

namespace ZxcRP.Runtime.Renderers
{
    /// <summary>
    /// Poly-Style渲染器
    /// </summary>
    public class PolyRenderer : Renderer
    {
        private CullingResults m_CullingResults;

        private static readonly ShaderTagId ZxcLitTagId = new ShaderTagId("ZxcLit");

        protected override void OnRender()
        {
            Setup();
            // Cull失败就返回，说明Camera有问题
            if (!Cull())
            {
                return;
            }

            SetupLight();
            DrawGeometry();
            Submit();
        }

        protected override void OnDispose()
        {

        }

        /// <summary>
        /// 开始渲染之前，需要做一些全局的Setup
        /// </summary>
        private void Setup()
        {
            m_Context.SetupCameraProperties(m_Camera);
            var clearFlags = m_Camera.clearFlags;
            m_CmdBuffer.ClearRenderTarget(
                clearFlags <= CameraClearFlags.Depth,
                clearFlags <= CameraClearFlags.SolidColor,
                m_Camera.backgroundColor.linear);
            m_CmdBuffer.BeginSample(GetName());
            // Setup与Camera相关的一些全局的shader变量
            m_CmdBuffer.SetViewProjectionMatrices(m_Camera.worldToCameraMatrix, m_Camera.projectionMatrix);
            m_CmdBuffer.SetGlobalVector("cameraPos", m_Camera.transform.position);
            ExecuteBuffer();
        }

        private bool Cull()
        {
            if(!m_Camera.TryGetCullingParameters(out var parameters))
            {
                return false;
            }

            m_CullingResults = m_Context.Cull(ref parameters);
            return true;
        }

        private void SetupLight()
        {
            var lights = m_CullingResults.visibleLights;
            var additionalIndex = 0;
            var positions = new Vector4[256];
            var directions = new Vector4[256];
            var colors = new Vector4[256]; 
            var ranges = new Vector4[256];
            var spots = new Vector4[256];
            for (var i = 0; i < lights.Length; ++i)
            {
                var light = lights[i];
                if (light.lightType == LightType.Directional)
                {
                    m_CmdBuffer.SetGlobalVector("mainLightDirection", -light.localToWorldMatrix.GetColumn(2));
                    m_CmdBuffer.SetGlobalVector("mainLightColor", light.finalColor.linear);
                }
                else
                {
                    positions[additionalIndex] = light.localToWorldMatrix.GetColumn(3);
                    directions[additionalIndex] = -light.localToWorldMatrix.GetColumn(2);
                    colors[additionalIndex] = light.finalColor.linear;
                    var rangeSqr = light.range * light.range;
                    var fadeDistance = 0.8f * 0.8f * rangeSqr;
                    var invRange = 1.0f / (fadeDistance - rangeSqr);
                    ranges[additionalIndex] = new Vector4(light.range, rangeSqr, invRange, -rangeSqr * invRange);
                    if (light.lightType == LightType.Spot)
                    {
                        var cosP = Mathf.Cos(light.light.innerSpotAngle * Mathf.Deg2Rad);
                        var cosU = Mathf.Cos(light.light.spotAngle * Mathf.Deg2Rad);
                        var invPU = 1.0f / (cosP - cosU);
                        spots[additionalIndex] = new Vector4(invPU, -cosU * invPU, 0.0f, 0.0f);
                    }
                    else
                    {
                        spots[additionalIndex] = new Vector4(1.0f, 1.0f, 0.0f, 0.0f);
                    }
                    m_CmdBuffer.SetGlobalVectorArray("additionalLightPositions", positions);
                    m_CmdBuffer.SetGlobalVectorArray("additionalLightDirections", directions);
                    m_CmdBuffer.SetGlobalVectorArray("additionalLightColors", colors);
                    m_CmdBuffer.SetGlobalVectorArray("additionalLightRanges", ranges);
                    m_CmdBuffer.SetGlobalVectorArray("additionalLightSpots", spots);
                    additionalIndex++;
                }
            }
            m_CmdBuffer.SetGlobalVector("additionalLightCount", new Vector4(additionalIndex, 0.0f, 0.0f, 0.0f));
        }

        private void DrawGeometry()
        {
            var sortingSetting = new SortingSettings(m_Camera)
            {
                criteria = SortingCriteria.CommonOpaque
            };
            var drawSetting = new DrawingSettings(ZxcLitTagId, sortingSetting);
            var filterSetting = new FilteringSettings(RenderQueueRange.opaque);
            m_Context.DrawRenderers(m_CullingResults, ref drawSetting, ref filterSetting);
        }

        private void ExecuteBuffer()
        {
            m_Context.ExecuteCommandBuffer(m_CmdBuffer);
            m_CmdBuffer.Clear();
        }

        private void Submit()
        {
            m_CmdBuffer.EndSample(GetName());
            ExecuteBuffer();
            m_Context.Submit();
        }

#if UNITY_EDITOR
        protected override string GetName()
        {
            return "PolyRenderer";
        }
#endif
    }
}